#include "log4qt_mgr.h"

#include <QDir>
#include "log4qt/logger.h"
#include "log4qt/propertyconfigurator.h"
#include "log4qt/patternlayout.h"
#include "log4qt/dailyrollingfileappender.h"

using namespace Log4Qt;

#include "aes.h"
#include "base64.h"

static const char g_key[17] = "0123456789123456";
static const char g_iv[17] = "0102030405060708";


/*
 * Function:    Log4qtMgr
 * Description: The default constructor
 * Paramer:     void
 * Return:
 */
Log4qtMgr::Log4qtMgr()
{
}

/*
 * Function:    ~Log4qtMgr
 * Description: The default destructor
 * Paramer:     void
 * Return:
 */
Log4qtMgr::~Log4qtMgr()
{
}

/*
 * Function:    GetInstance
 * Description: Get an instance of "Log4qtMgr" class, if exist, return it
 * Paramer:     QObject *pParent - the pointer of QObject class
 * Return:      the pointer of "Log4qtMgr" class
 */
Log4qtMgr* Log4qtMgr::GetInstance()
{
    static Log4qtMgr *pNew = NULL;
    if(!pNew) {
        qDebug() << Q_FUNC_INFO;

        // Create a new object if it doesn't exist
        pNew = new Log4qtMgr();
        if(NULL == pNew) return NULL;

        // After constructing object, initialize the object
        pNew->_Initialze();
    }

    return pNew;
}

/*
 * Function:    _Initialze
 * Description: After constructing object, initialize the object
 * Paramer:     void
 * Return:      void
 */
void Log4qtMgr::_Initialze()
{
    QString sDirPath = QApplication::applicationDirPath();
    QString sLogCfgPath = sDirPath + DF_LOG_CFG_FILE;
    Log4Qt::PropertyConfigurator::configure(sLogCfgPath);

//    QString sLogFile = QApplication::applicationDirPath() + "/logs/" + QDate::currentDate().toString("yyyyMMdd");
//    QDir dirLog(sLogFile);
//    if(!dirLog.exists()){
//        dirLog.mkdir(sLogFile);
//    }
}

/*
 * Function:    Output
 * Description: Output data to the terminal
 * Paramer:     QtMsgType mgsType - the log type
 *              QString msgText - the message info
 * Return:      void
 */
void Log4qtMgr::Output(QtMsgType mgsType, const QString & msgText)
{
    QString msgTextEx = "";

#ifndef DEF_DEBUG_PC
//    msgTextEx = EncryptionAES(msgText);
    msgTextEx = msgText;
#else
    msgTextEx = msgText;
#endif

    switch (mgsType) {
    case QtDebugMsg:
        Log4Qt::Logger::rootLogger()->debug(QString("%1").arg(msgTextEx));
        break;
    case QtInfoMsg:
        Log4Qt::Logger::rootLogger()->info(QString("%1").arg(msgTextEx));
        break;
    case QtWarningMsg:
        Log4Qt::Logger::rootLogger()->warn(QString("%1").arg(msgTextEx));
        break;
    case QtCriticalMsg:
        Log4Qt::Logger::rootLogger()->error(QString("%1").arg(msgTextEx));
        break;
    case QtFatalMsg:
        Log4Qt::Logger::rootLogger()->fatal(QString("%1").arg(msgTextEx));
        break;
    default:
        Log4Qt::Logger::rootLogger()->info(QString("%1").arg(msgTextEx));
        break;
    }
}

/*
 * Function:    Output
 * Description: Output data to the terminal
 * Paramer:     QtMsgType mgsType - the log type
 *              const QMessageLogContext & msgContext - the log context
 *              QString msgText - the message info
 * Return:      void
 */
void Log4qtMgr::Output(QtMsgType mgsType, const QMessageLogContext & msgContext, const QString & msgText)
{
    QString msgTextEx = "";

#ifndef DEF_DEBUG_PC
    msgTextEx = EncryptionAES(msgText);
#else
    msgTextEx = msgText;
#endif

    qDebug() << DF_CUR_TIME_EX << msgText;
    return;

    switch (mgsType) {
    case QtDebugMsg:
        Log4Qt::Logger::rootLogger()->debug(QString("%1 \"%2 %3\"").arg(msgContext.function).arg(QtDebugMsg).arg(msgTextEx));
        break;
    case QtInfoMsg:
        Log4Qt::Logger::rootLogger()->info(QString("%1 \"%2 %3\"").arg(msgContext.function).arg(QtInfoMsg).arg(msgTextEx));
        break;
    case QtWarningMsg:
        Log4Qt::Logger::rootLogger()->warn(QString("%1 \"%2 %3\"").arg(msgContext.function).arg(QtWarningMsg).arg(msgTextEx));
        break;
    case QtCriticalMsg:
        Log4Qt::Logger::rootLogger()->trace(QString("%1 \"%2 %3\"").arg(msgContext.function).arg(QtCriticalMsg).arg(msgTextEx));
        break;
    case QtFatalMsg:
        Log4Qt::Logger::rootLogger()->fatal(QString("%1 \"%2 %3\"").arg(msgContext.function).arg(QtFatalMsg).arg(msgTextEx));
        break;
    default:
        Log4Qt::Logger::rootLogger()->error(QString("%1 \"%2 %3\"").arg(msgContext.function).arg(QtInfoMsg).arg(msgTextEx));
        break;
    }
}

/*
* Function:		GetErrorLevelByType
* Description:	Get the error level info by error type
* Paramer:		int nErrType - the error type number
* Return:		QString - the error level
*/
QString Log4qtMgr::GetErrorLevelByType(int nErrType)
{
    qDebug() << Q_FUNC_INFO << nErrType;
    QString sErrLevel;

    switch (nErrType) {
    case 1:
        sErrLevel = tr("Info");
        break;
    case 2:
        sErrLevel = tr("Warn");
        break;
    case 3:
        sErrLevel = tr("Fault");
        break;
    case 4:
        sErrLevel = tr("Exception");
        break;
    default:
        sErrLevel = tr("Info");
        break;
    }

    qDebug() << Q_FUNC_INFO << sErrLevel;
    return sErrLevel;
}

/*
* Function:		GetErrorDescByCode
* Description:	Get the error desc info by error number
* Paramer:		int nErrNum - the error number
*               bool bHex - is hex format?
* Return:		QString - the error desc
*/
QString Log4qtMgr::GetErrorDescByCode(int nErrNum, bool bHex)
{
    qDebug() << Q_FUNC_INFO << nErrNum << bHex;
    QString sErrDesc;

    if(bHex) {
        // Hexadecimal to decimal
        bool bOk = false;
        nErrNum = QString::number(nErrNum, 10).toInt(&bOk, 16);
    }

    bool bErrFlag = false;
    if(0 < (nErrNum & 0x1)) { sErrDesc = tr("0.MC FOC Duration"); bErrFlag = true; }
    if(0 < (nErrNum & 0x2)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + ("1.MC Over Voltage"); bErrFlag = true; }
    if(0 < (nErrNum & 0x4)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("2.MC Under Voltage"); bErrFlag = true; }
    if(0 < (nErrNum & 0x8)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("3.MC Over Heat"); bErrFlag = true; }
    if(0 < (nErrNum & 0x10)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("4.MC StartUp"); bErrFlag = true; }
    if(0 < (nErrNum & 0x20)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("5.MC Speed Feedback"); bErrFlag = true; }
    if(0 < (nErrNum & 0x40)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("6.MC Over Current"); bErrFlag = true; }
    if(0 < (nErrNum & 0x80)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("7.MC Software Error"); bErrFlag = true; }
    if(0 < (nErrNum & 0x100)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("8.Tray Sensor Error"); bErrFlag = true; }
    if(0 < (nErrNum & 0x200)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("9.Lifting Sensor Error"); bErrFlag = true; }
    if(0 < (nErrNum & 0x400)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("10.Reagent Card Type Error"); bErrFlag = true; }
    if(0 < (nErrNum & 0x800)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("11.Wave data Calculate Error"); bErrFlag = true; }
    if(0 < (nErrNum & 0x1000)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("12.Scan code Error"); bErrFlag = true; }
    if(0 < (nErrNum & 0x2000)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("13.MC Alignment Error"); bErrFlag = true; }
    if(0 < (nErrNum & 0x4000)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("14.Upper PD Full"); bErrFlag = true; }
    if(0 < (nErrNum & 0x8000)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("15.Lower PD Full"); bErrFlag = true; }
    if(0 < (nErrNum & 0x10000)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("16.LED Board Communication Error"); bErrFlag = true; }
    if(0 < (nErrNum & 0x20000)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("17.Single Card Assemble Asymmetrically"); bErrFlag = true; }
    if(0 < (nErrNum & 0x40000)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("18.External Flash Error"); bErrFlag = true; }
    if(0 < (nErrNum & 0x80000)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("19.Sampling Speed Stability Timeout"); bErrFlag = true; }
    if(0 < (nErrNum & 0x100000)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("20.Sampling Speed Error"); bErrFlag = true; }
    if(0 < (nErrNum & 0x200000)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("21.ID Chip Not Inserted"); bErrFlag = true; }
    if(0 < (nErrNum & 0x400000)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("22.Accelerometer Communication Error"); bErrFlag = true; }
    if(0 < (nErrNum & 0x800000)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("23.Accelerometer Detected Shaking"); bErrFlag = true; }
    if(0 < (nErrNum & 0x1000000)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("24.White Background Out of Range"); bErrFlag = true; }
    if(0 < (nErrNum & 0x8000000)) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("27.Temperature sensor abnormal"); bErrFlag = true; }

    if(0x10000000  == nErrNum) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("IAP upgrade failed"); bErrFlag = true; }
    if(0x10000001  == nErrNum) {sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("Instrument preheating failed"); bErrFlag = true;}
    if(0x10000002  == nErrNum) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("The ID chip does not match the reagent disk. Please replace the ID chip"); bErrFlag = true;}
    if(0x10000003  == nErrNum) { sErrDesc = sErrDesc + (sErrDesc.isEmpty() ? "" : ";") + tr("Insufficient reagent quantity of sample"); bErrFlag = true; }

    if(!bErrFlag) { sErrDesc = tr("Other errors"); }

    qDebug() << Q_FUNC_INFO << sErrDesc;
    return sErrDesc;
}

/*
 * Function:    EncryptionAES
 * Description: AES-128-CBC encryption
 * Paramer:     const QString strSrc - the source string
 * Return:      QString - the target string
 */
QString Log4qtMgr::EncryptionAES(const QString strSrc)
{
    size_t length = strSrc.toStdString().length();
    int block_num = length / BLOCK_SIZE + 1;

    // Plaintext
    char* szDataIn = new char[block_num * BLOCK_SIZE + 1];
    memset(szDataIn, 0x00, block_num * BLOCK_SIZE + 1);
    strcpy(szDataIn, strSrc.toStdString().c_str());

    // PKCS7Padding
    int k = length % BLOCK_SIZE;
    int j = length / BLOCK_SIZE;
    int padding = BLOCK_SIZE - k;
    for (int i = 0; i < padding; i++) {
        szDataIn[j * BLOCK_SIZE + k + i] = padding;
    }
    szDataIn[block_num * BLOCK_SIZE] = '\0';

    // Encrypted ciphertext
    char *szDataOut = new char[block_num * BLOCK_SIZE + 1];
    memset(szDataOut, 0, block_num * BLOCK_SIZE + 1);

    // Performing CBC schema encryption for AES
    AES aes;
    aes.MakeKey(g_key, g_iv, 16, 16);
    aes.Encrypt(szDataIn, szDataOut, block_num * BLOCK_SIZE, AES::CBC);
    string str = base64_encode((unsigned char*) szDataOut, block_num * BLOCK_SIZE);
    delete[] szDataIn;
    delete[] szDataOut;

    return QString::fromStdString(str);
}

/*
 * Function:    DecryptionAES
 * Description: AES-128-CBC decrypt
 * Paramer:     const QString strSrc - the source string
 * Return:      QString - the target string
 */
QString Log4qtMgr::DecryptionAES(const QString strSrc)
{
    string strData = base64_decode(strSrc.toStdString());
    size_t length = strData.length();
    // Ciphertext
    char *szDataIn = new char[length + 1];
    memcpy(szDataIn, strData.c_str(), length+1);

    // Plaintext
    char *szDataOut = new char[length + 1];
    memcpy(szDataOut, strData.c_str(), length+1);

    // CBC mode decryption of AES
    AES aes;
    aes.MakeKey(g_key, g_iv, 16, 16);
    aes.Decrypt(szDataIn, szDataOut, length, AES::CBC);

    // PKCS7Padding
    if ((0x00 < szDataOut[length - 1]) && (szDataOut[length - 1] <= 0x16)) {
        int tmp = szDataOut[length - 1];
        for (int i = (int)length - 1; i >= (int)(length - tmp); i--) {
            if (szDataOut[i] != tmp) {
                memset(szDataOut, 0, length);
                break;
            }
            else
                szDataOut[i] = 0;
        }
    }
    string strDest(szDataOut);
    delete[] szDataIn;
    delete[] szDataOut;

    return QString::fromStdString(strDest);
}
